package com.ganbing518.commons.utils;

import sun.security.krb5.internal.crypto.Des;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.DESKeySpec;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.GeneralSecurityException;
import java.security.SecureRandom;

public class MacUtils {

    /**
     * 计算MAC(hex) PBOC_3DES_MAC(符合ISO9797Alg3Mac标准)
     * (16的整数补8000000000000000) 前n-1组使用单长密钥DES 使用密钥是密钥的左8字节） 最后1组使用双长密钥3DES （使用全部16字节密钥）
     *
     * 算法步骤：初始数据为D，初始向量为I，3DES秘钥为K0，秘钥低8字节DES秘钥K1；
     * 1、数据D分组并且填充：将字节数组D进行分组，每组8个字节，分组编号从0开始,分别为D0...Dn；最后一个分组不满8字节的，先填充一个字节80，后续全部填充00，满8字节的，新增一个8字节分组（80000000 00000000）；
     * 2、进行des循环加密：（1）D0和初始向量I进行按位异或得到结果O0;(2)使用秘钥K1，DES加密结果O0得到结果I1,将I1和D1按位异或得到结果D1；(3)循环第二步骤得到结果Dn；
     * 3、将Dn使用16字节秘钥K0进行3DES加密，得到的结果就是我们要的MAC。
     * @param data 带计算的数据
     * @param key 16字节密钥
     * @param icv 算法向量
     * @return mac签名
     * @throws Exception
     */
    public static byte[] calculatePboc3desMAC(byte[] data, byte[] key, byte[] icv) throws Exception {

        if (key == null || data == null) {
            throw new RuntimeException("data or key is null.");
        }
        if (key.length != 16) {
            throw new RuntimeException("key length is not 16 byte.");
        }

        byte[] leftKey = new byte[8];
        System.arraycopy(key, 0, leftKey, 0, 8);

        // 拆分数据（8字节块/Block）

        final int dataLength = data.length;
        final int blockCount = dataLength / 8 + 1;
        final int lastBlockLength = dataLength % 8;

        byte[][] dataBlock = new byte[blockCount][8];

        for (int i = 0; i < blockCount; i++) {
            int copyLength = i == blockCount - 1 ? lastBlockLength : 8;
            System.arraycopy(data, i * 8, dataBlock[i], 0, copyLength);
        }

        dataBlock[blockCount - 1][lastBlockLength] = (byte) 0x80;

        byte[] desXor = xOr(dataBlock[0], icv);
        for (int i = 1; i < blockCount; i++) {
            byte[] des = encryptByDesCbc(desXor, leftKey);
            desXor = xOr(dataBlock[i], des);
        }

        desXor = encryptBy3DesCbc(desXor, key);
        return desXor;
    }

    /**
     * ANSI X9.9MAC算法  <br/>
     * (1) ANSI X9.9MAC算法只使用单倍长密钥。  <br/>
     * (2)  MAC数据先按8字节分组，表示为D0～Dn，如果Dn不足8字节时，尾部以字节00补齐。 <br/>
     * (3) 用MAC密钥加密D0，加密结果与D1异或作为下一次的输入。 <br/>
     * (4) 将上一步的加密结果与下一分组异或，然后再用MAC密钥加密。<br/>
     * (5) 直至所有分组结束，取最后结果的左半部作为MAC。<br/>
     * 采用x9.9算法计算MAC (Count MAC by ANSI-x9.9).
     *
     * @param key  8字节密钥数据
     * @param data 待计算的缓冲区
     * @throws GeneralSecurityException
     */
    public static byte[] calculateANSIX9_9MAC(byte[] key, byte[] data) throws GeneralSecurityException {

        final int dataLength = data.length;
        final int lastLength = dataLength % 8;
        final int lastBlockLength = lastLength == 0 ? 8 : lastLength;
        final int blockCount = dataLength / 8 + (lastLength > 0 ? 1 : 0);

// 拆分数据（8字节块/Block）
        byte[][] dataBlock = new byte[blockCount][8];
        for (int i = 0; i < blockCount; i++) {
            int copyLength = i == blockCount - 1 ? lastBlockLength : 8;
            System.arraycopy(data, i * 8, dataBlock[i], 0, copyLength);
        }

        byte[] desXor = new byte[8];
        for (int i = 0; i < blockCount; i++) {
            byte[] tXor = xOr(desXor, dataBlock[i]);
            desXor = encryptByDesEcb(tXor, key); // DES加密
        }
        return desXor;
    }

    /**
     * 采用ANSI x9.19算法计算MAC (Count MAC by ANSI-x9.19).<br/>
     * 将ANSI X9.9的结果做如下计算<br/>
     * (6) 用MAC密钥右半部解密(5)的结果。 <br/>
     * (7) 用MAC密钥左半部加密(6)的结果。<br/>
     * (8) 取(7)的结果的左半部作为MAC。<br/>
     * @param key  16字节密钥数据
     * @param data 待计算的缓冲区
     * @throws GeneralSecurityException
     */
    public static byte[] calculateANSIX9_19MAC(byte[] key, byte[] data) throws GeneralSecurityException {
        if (key == null || data == null)
            return null;

        if (key.length != 16) {
            throw new RuntimeException("秘钥长度错误.");
        }

        byte[] keyLeft = new byte[8];
        byte[] keyRight = new byte[8];
        System.arraycopy(key, 0, keyLeft, 0, 8);
        System.arraycopy(key, 8, keyRight, 0, 8);

        byte[] result99 = calculateANSIX9_9MAC(keyLeft, data);

        byte[] resultTemp = decryptByDesEcb(result99, keyRight);
        return encryptByDesEcb(resultTemp, keyLeft);
    }

    public static final byte[] ZERO_IVC = new byte[]{0, 0, 0, 0, 0, 0, 0, 0};

    /**
     * 生成秘钥
     *
     * @return 16字节3des秘钥
     * @throws GeneralSecurityException
     */
    public static byte[] create3DESKey() throws GeneralSecurityException {
        KeyGenerator kg = KeyGenerator.getInstance("DESede");
        kg.init(112);//must be equal to 112 or 168
        byte[] key24 = kg.generateKey().getEncoded();
        byte[] result = new byte[16];
        System.arraycopy(key24, 0, result, 0, 16);
        return result;
    }


    /**
     * 3DES加密cbc模式
     *
     * @param content 待加密数据
     * @param key     秘钥
     * @param ivb     向量
     * @return 加密结果
     * @throws GeneralSecurityException
     */
    public static byte[] encryptBy3DesCbc(byte[] content, byte[] key, byte[] ivb) throws GeneralSecurityException {
        byte[] _3deskey = new byte[24];
        System.arraycopy(key, 0, _3deskey, 0, 16);
        System.arraycopy(key, 0, _3deskey, 16, 8);

        Cipher cipher = Cipher.getInstance("DESede/CBC/NoPadding");
        SecretKey secureKey = new SecretKeySpec(_3deskey, "DESede");
        IvParameterSpec iv = new IvParameterSpec(ivb);
        cipher.init(Cipher.ENCRYPT_MODE, secureKey, iv);
        return cipher.doFinal(content);
    }

    /**
     * 3DES解密cbc模式
     *
     * @param content 待解密数据
     * @param key     秘钥
     * @param ivb     向量
     * @return 解密结果
     * @throws GeneralSecurityException
     */
    public static byte[] decryptBy3DesCbc(byte[] content, byte[] key, byte[] ivb) throws GeneralSecurityException {
        byte[] _3deskey = new byte[24];
        System.arraycopy(key, 0, _3deskey, 0, 16);
        System.arraycopy(key, 0, _3deskey, 16, 8);

        Cipher cipher = Cipher.getInstance("DESede/CBC/NoPadding");
        SecretKey secureKey = new SecretKeySpec(_3deskey, "DESede");
        IvParameterSpec iv = new IvParameterSpec(ivb);
        cipher.init(Cipher.DECRYPT_MODE, secureKey, iv);
        return cipher.doFinal(content);
    }

    /**
     * 3DES加密cbc模式，默认向量
     *
     * @param content 待加密数据
     * @param key     秘钥
     * @return 加密结果
     * @throws GeneralSecurityException
     */
    public static byte[] encryptBy3DesCbc(byte[] content, byte[] key) throws GeneralSecurityException {
        return encryptBy3DesCbc(content, key, ZERO_IVC);
    }

    /**
     * 3DES解密cbc模式，默认向量
     *
     * @param content 带解密数据
     * @param key     秘钥
     * @return 解密结果
     * @throws GeneralSecurityException
     */
    public static byte[] decryptBy3DesCbc(byte[] content, byte[] key) throws GeneralSecurityException {
        return decryptBy3DesCbc(content, key, ZERO_IVC);
    }


    /**
     * 3DES加密Ecb模式
     *
     * @param content 待加密数据
     * @param key     加密秘钥
     * @return 加密结果
     * @throws GeneralSecurityException
     */
    public static byte[] encryptBy3DesEcb(byte[] content, byte[] key) throws GeneralSecurityException {
        byte[] _3deskey = new byte[24];
        System.arraycopy(key, 0, _3deskey, 0, 16);
        System.arraycopy(key, 0, _3deskey, 16, 8);

        Cipher cipher = Cipher.getInstance("DESede/ECB/NoPadding");
        SecretKey secureKey = new SecretKeySpec(_3deskey, "DESede");
        cipher.init(Cipher.ENCRYPT_MODE, secureKey);
        return cipher.doFinal(content);
    }

    /**
     * 3DES解密Ecb模式
     *
     * @param content 待解密数据
     * @param key     秘钥
     * @return 解密结果
     * @throws GeneralSecurityException
     */
    public static byte[] decryptBy3DesEcb(byte[] content, byte[] key) throws GeneralSecurityException {
        byte[] _3deskey = new byte[24];
        System.arraycopy(key, 0, _3deskey, 0, 16);
        System.arraycopy(key, 0, _3deskey, 16, 8);

        Cipher cipher = Cipher.getInstance("DESede/ECB/NoPadding");
        SecretKey secureKey = new SecretKeySpec(_3deskey, "DESede");
        cipher.init(Cipher.DECRYPT_MODE, secureKey);
        return cipher.doFinal(content);
    }


    /**
     * des的cbc模式加密算法
     *
     * @param content 待加密数据
     * @param key     密钥
     * @return 加密结果
     * @throws GeneralSecurityException
     */
    public static byte[] encryptByDesCbc(byte[] content, byte[] key) throws GeneralSecurityException {
        return encryptByDesCbc(content, key, ZERO_IVC);
    }

    /**
     * des的cbc模式解密算法
     *
     * @param content 待解密数据
     * @param key     密钥
     * @return 解密结果
     * @throws GeneralSecurityException
     */
    public static byte[] decryptByDesCbc(byte[] content, byte[] key) throws GeneralSecurityException {
        return decryptByDesCbc(content, key, ZERO_IVC);
    }


    /**
     * des的cbc模式加密算法
     *
     * @param content 待加密数据
     * @param key     加密密钥
     * @return 加密结果
     * @throws GeneralSecurityException
     */
    public static byte[] encryptByDesCbc(byte[] content, byte[] key, byte[] icv) throws GeneralSecurityException {
        SecureRandom sr = new SecureRandom();
        DESKeySpec dks = new DESKeySpec(key);
        SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
        SecretKey secretKey = keyFactory.generateSecret(dks);
        Cipher cipher = Cipher.getInstance("DES/CBC/NoPadding");
        IvParameterSpec iv = new IvParameterSpec(icv);

        cipher.init(Cipher.ENCRYPT_MODE, secretKey, iv, sr);

        return cipher.doFinal(content);
    }

    /**
     * des的cbc模式解密算法
     *
     * @param content 待解密数据
     * @param key     密钥
     * @return 解密结果
     * @throws GeneralSecurityException
     */
    public static byte[] decryptByDesCbc(byte[] content, byte[] key, byte[] icv) throws GeneralSecurityException {
        SecureRandom sr = new SecureRandom();
        DESKeySpec dks = new DESKeySpec(key);
        SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
        SecretKey secretKey = keyFactory.generateSecret(dks);
        Cipher cipher = Cipher.getInstance("DES/CBC/NoPadding");
        IvParameterSpec iv = new IvParameterSpec(icv);

        cipher.init(Cipher.DECRYPT_MODE, secretKey, iv, sr);

        return cipher.doFinal(content);
    }

    /**
     * des加密算法，ECB方式，NoPadding模式，数据字节必须是8的整数倍
     *
     * @param key
     * @param data 数据字节必须是8的整数倍
     * @return 加密结果
     * @throws GeneralSecurityException
     */
    public static byte[] encryptByDesEcb(byte[] content, byte[] key) throws GeneralSecurityException {
        Cipher cipher = Cipher.getInstance("DES/ECB/NoPadding");
        SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
        SecretKey secretKey = keyFactory.generateSecret(new DESKeySpec(key));
        cipher.init(Cipher.ENCRYPT_MODE, secretKey);
        return cipher.doFinal(content);
    }

    /**
     * des解密算法，ECB方式，NoPadding模式，数据字节必须是8的整数倍
     *
     * @param key  秘钥
     * @param data 数据字节必须是8的整数倍
     * @return
     * @throws GeneralSecurityException
     */
    public static byte[] decryptByDesEcb(byte[] content, byte[] key) throws GeneralSecurityException {
        Cipher cipher = Cipher.getInstance("DES/ECB/NoPadding");
        SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
        SecretKey secretKey = keyFactory.generateSecret(new DESKeySpec(key));
        cipher.init(Cipher.DECRYPT_MODE, secretKey);
        return cipher.doFinal(content);
    }

    /**
     * 本项目用于生成外部设备认证码和校验卡认证码（javacard GP规范 SCP02安全通道）（cardCryptogram）
     * B.1.2.1  Full Triple DES MAC
     * The full triple DES MAC is as defined in [ISO 9797-1] as MAC Algorithm 1 with output transformation 3,
     * without truncation, and with triple DES taking the place of the block cipher.
     *
     * @param content 待加密数据
     * @param key     加密密钥
     * @return 加密结果后8字节
     * @throws Exception
     */
    public static byte[] encryptBy3DesCbcLast8Mac(byte[] content, byte[] key) throws GeneralSecurityException {
        byte[] edata = encryptBy3DesCbc(content, key);

        byte[] result = new byte[8];
        System.arraycopy(edata, edata.length - 8, result, 0, 8);
        return result;
    }

    /**
     * 将b1和b2做异或，然后返回
     *
     * @param b1
     * @param b2
     * @return 异或结果
     */
    public static byte[] xOr(byte[] b1, byte[] b2) {
        byte[] tXor = new byte[Math.min(b1.length, b2.length)];
        for (int i = 0; i < tXor.length; i++)
            tXor[i] = (byte) (b1[i] ^ b2[i]); // 异或(Xor)
        return tXor;
    }

    /**
     * 整形转字节
     *
     * @param n      整形数值
     * @param buf    结果字节数组
     * @param offset 填充开始位置
     */
    public static void int2byte(int n, byte buf[], int offset) {
        buf[offset] = (byte) (n >> 24);
        buf[offset + 1] = (byte) (n >> 16);
        buf[offset + 2] = (byte) (n >> 8);
        buf[offset + 3] = (byte) n;
    }

    /**
     * 长整形转字节
     *
     * @param n      长整形数值
     * @param buf    结果字节数组
     * @param offset 填充开始位置
     */
    public static void long2byte(long n, byte buf[], int offset) {
        buf[offset] = (byte) (int) (n >> 56);
        buf[offset + 1] = (byte) (int) (n >> 48);
        buf[offset + 2] = (byte) (int) (n >> 40);
        buf[offset + 3] = (byte) (int) (n >> 32);
        buf[offset + 4] = (byte) (int) (n >> 24);
        buf[offset + 5] = (byte) (int) (n >> 16);
        buf[offset + 6] = (byte) (int) (n >> 8);
        buf[offset + 7] = (byte) (int) n;
    }
}
